package obecto.utility
{
	import flash.events.Event;
	import flash.utils.Dictionary;
	
	import obecto.core.FLGlobals;
	
	public class InvalidationUtility
	{
		/**
		 * By default the InvalidationUtility works in strict mode - this simply means,
		 * that when a non-existent property name is requested it will trigger a run-time 
		 * exception. 
		 * 
		 * The existence check is quite performance intensive, so we recommend when building a 
		 * release to switch to non-strict mode and skip the property existence check. 
		 */ 
		public static var strictMode : Boolean = true;
		
		/**
		 * This is the component which holds the properties we follow for changes.
		 */ 
		public var component : Object;
		
		/**
		 * This is the method where all sequential couplings between the component's properties are resolved.
		 */ 
		public var validationFunction : Function;
		
		private var oldPropertiesValues : Dictionary = new Dictionary();
		private var currentPropertiesValues : Dictionary = new Dictionary();
		
		private function get isComponentChanged() : Boolean
		{
			var result : Boolean = false;
			for (var propertyName : * in oldPropertiesValues)
			{
				if (isPropertyChanged(propertyName))
				{
					result = true;
					break;
				}
			}
			return result;
		}
		
		public function isPropertyChanged(propertyName : String) : Boolean
		{
			if (strictMode) checkProperty(propertyName);
			
			return currentPropertiesValues[propertyName] != oldPropertiesValues[propertyName];
		}
		
		public function setProperty(propertyName : String, value : *) : void 
		{
			if (strictMode) checkProperty(propertyName);
			
			if (value != currentPropertiesValues[propertyName])
			{
				oldPropertiesValues[propertyName] = currentPropertiesValues[propertyName];
				currentPropertiesValues[propertyName] = value;
				
				scheduleValidation();
			}
		}
		
		public function getProperty(propertyName : String) : Object
		{
			if (strictMode) checkProperty(propertyName);
			
			return currentPropertiesValues[propertyName];
		}
		
		private function scheduleValidation() : void 
		{
			FLGlobals.topLevelSprite.addEventListener(Event.RENDER, handleRenderEvent);
			FLGlobals.topLevelSprite.stage.invalidate();
		}
		
		private function reset() : void 
		{
			FLGlobals.topLevelSprite.removeEventListener(Event.RENDER, handleRenderEvent);
			
			for (var propertyName : * in oldPropertiesValues)
			{
				delete oldPropertiesValues[propertyName];
			}
		}
		
		private function checkProperty(propertyName : String) : void 
		{
			if (!ReflectionUtility.checkAccessor(propertyName, component))
			{
				throw new Error("No such property: " + propertyName);
			}
		}
		
		private function handleRenderEvent(e : Event) : void
		{
			if (isComponentChanged)
			{
				validationFunction();
				reset();
			}
		}

	}
}